/*
* Licensed under the MIT License
* 
* Copyright (c) 2010 Kay Kreuning
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package nl.hyves.api.rpc
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	
	import nl.hyves.api.errors.HyvesError;
	import nl.hyves.api.events.HyvesErrorEvent;
	import nl.hyves.api.events.HyvesServiceEvent;
	
	/**
	 * Dispatched when a reqeusts is completed
	 * 
	 * @eventType flash.events.Event.COMPLETE
	 */
	[Event(name="complete", type="nl.hyves.api.events.HyvesServiceEvent")]
	
	/**
	 * Dispatched when a reqeusts returned an error
	 * 
	 * @eventType nl.hyves.api.events.HyvesErrorEvent
	 */
	[Event(name="apiError", type="nl.hyves.api.events.HyvesErrorEvent")]
	
	/**
	 * Dispatched when an io error occurs
	 * 
	 * @eventType flash.events.IOErrorEvent
	 */
	[Event(name="ioError", type="flash.events.IOErrorEvent")]
	
	/**
	 * Token used as a stand-in for remote calls, event listeners and responders can be attached to this individual token.
	 * 
	 * @author Kay Kreuning
	 */
	public final class HyvesAsyncToken extends EventDispatcher implements IHyvesAsyncToken
	{
		private var responders:Array = new Array();
		private var isResponseValid:Boolean;
		private var response:String;
		private var _data:Object;
		
		private var urlRequest:URLRequest;
		
		/**
		 * @inheritDoc
		 */
		public function get data():Object
		{
			return _data;
		}
		
		/**
		 * @inheritDoc
		 */
		public function get rawResponse():String
		{
			return response;
		}
		
		/**
		 * Constructor.
		 * 
		 * @param urlRequest URLRequest to perform
		 */
		public function HyvesAsyncToken(urlRequest:URLRequest)
		{
			this.urlRequest = urlRequest;
			
			var urlLoader:URLLoader = new URLLoader(urlRequest);
			urlLoader.addEventListener(Event.COMPLETE, onComplete);
			urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
		}
		
		/**
		 * @inheritDoc
		 */
		public function hasResponder():Boolean
		{
			return responders ? responders.length > 0 : false;
		}
		
		/**
		 * @inheritDoc
		 */
		public function addResponder(result:Function, fault:Function = null):void
		{
			// If there is already data available we can call the response immediately:
			if (data)
				isResponseValid ? result(data) : fault(data);
			else
				responders.push(new Responder(result, fault));
		}
		
		private function onComplete(event:Event):void
		{
			// Assign the raw response:
			response = event.target.data;
			
			// Validate the response
			if (!(/<error_result>/).test(response))
			{
				// We are good:
				isResponseValid = true;
				
				_data = XMLDecoder.decode(new XML(response));
				
				while (hasResponder())
				{
					responders.pop().result.apply(null, [data]);
				}
				
				dispatchEvent(new HyvesServiceEvent(HyvesServiceEvent.COMPLETE, data));
			}
			else
			{
				// We have an error, parse and return:
				isResponseValid = true;
				
				var xml:XML = new XML(response);
				
				var error:HyvesError = new HyvesError(
					xml.error_message.toString(),
					parseInt(xml.error_code.toString()),
					xml.request_parameters.parameter.(requestkey == "ha_method").requestvalue.toString()
				);
				
				_data = error;
				
				// Invoke responders (if any):
				while (hasResponder())
				{
					var responder:Responder = responders.pop();
					
					// A responder's fault method can be null, check first:
					if (responder.fault != null)
						responder.fault.apply(null, [data]);
				}
				
				dispatchEvent(new HyvesErrorEvent(HyvesErrorEvent.API_ERROR, error.message, error.code, error.method));
			}
		}
		
		private function onIOError(event:IOErrorEvent):void
		{
			if (hasEventListener(IOErrorEvent.IO_ERROR))
				dispatchEvent(event.clone());
		}
	}
}

/**
 * @private
 */
internal final class Responder
{
	public var result:Function;
	public var fault:Function;
	
	public function Responder(result:Function, fault:Function)
	{
		this.result = result;
		this.fault = fault;
	}
}